跳到主要内容

Golang 的 JS 解释器之 otto 包

这个 otto 包是一个第三方的 JS 解析器,看到了另一个解析器 goja 也很不错(作者说性能更好),但是因为最近用到了 otto,所以这里单独学习一下

go get -u "github.com/robertkrimen/otto"

简单使用例

package main

import (
"fmt"

"github.com/robertkrimen/otto"
)

func main() {
vm := otto.New()
// Run something in the VM
vm.Run(`
abc = 2 + 2;
console.log("The value of abc is " + abc); // 4
`)

// Get a value out of the VM
if value, err := vm.Get("abc"); err == nil {
if value_int, err := value.ToInteger(); err == nil {
fmt.Println("The value is ", value_int)
}
}

// Set a number
vm.Set("def", 11)
vm.Run(`
console.log("The value of def is " + def);
// The value of def is 11
`)

// Set a Go function
vm.Set("sayHello", func(call otto.FunctionCall) otto.Value {
fmt.Printf("Hello, %s.\n", call.Argument(0).String())
return otto.Value{}
})

vm.Set("twoPlus", func(call otto.FunctionCall) otto.Value {
right, _ := call.Argument(0).ToInteger()
result, _ := vm.ToValue(2 + right)
return result
})

// Use the functions in JavaScript(它会返回最后一个变量的值)
result02, _ := vm.Run(`
sayHello("Xyzzy"); // Hello, Xyzzy.
sayHello(); // Hello, undefined

result = twoPlus(3.0); // 5
res02 = result * 100; // 500
`)

fmt.Println("The value of result02 is", result02) // 500
}

运行时长限制

otto 是 golang 的 js 虚拟机,基本支持正常的 js 代码,但 re2 或者 es6 这些现代化特性暂不支持。由于是虚拟机,因此需要限制脚本运行最长时间:

package main

import (
"errors"
"fmt"
"os"
"time"

"github.com/robertkrimen/otto"
)

var halt = errors.New("Stahp")

func main() {
runUnsafe(`var abc = [];`)
runUnsafe(`
while (true) {
// Loop forever
}`)
}

func runUnsafe(unsafe string) {
start := time.Now()
defer func() {
duration := time.Since(start)
if caught := recover(); caught != nil {
if caught == halt {
fmt.Fprintf(os.Stderr, "Some code took to long! Stopping after: %v\n", duration)
return
}
panic(caught) // Something else happened, repanic!
}
fmt.Fprintf(os.Stderr, "Ran code successfully: %v\n", duration)
}()

vm := otto.New()
vm.Interrupt = make(chan func(), 1) // The buffer prevents blocking

go func() {
time.Sleep(2 * time.Second) // Stop after two seconds
vm.Interrupt <- func() {
panic(halt)
}
}()

vm.Run(unsafe) // Here be dragons (risky code)
}

这种方法对应超时的脚本不会产生问题,然而,如果这个脚本正常跑完了,那么推入 Interrupt 里的 东西就不会被拉出来,从而导致 goroutine 一直不被释放。

因此这种写法还需要增加一个容错处理

...
// 增加一个额外的状态,如果otto没有自动拉出来,那么就在正常结束时我来处理
state := make(chan int,1)
state<-0

go func() {
if <-state==0 {
state<-2
time.Sleep(2 * time.Second) // Stop after two seconds
vm.Interrupt <- func() {
panic(halt)
}
}
}()

vm.Run(unsafe) // Here be dragons (risky code)

// 超过时间也会执行到这里,如果没有超过时间,那么取出的是0,否则取出的是2
if <-state==0{state<-1}
close(state)
...

References

otto踩坑:vm.Interrupt导致goroutine无限增加